#include <omnitrix/console.h>
#include <omnitrix/io.h>
#include <omnitrix/string.h>

#define CRT_ADDR_REG 0x3D4  // CRT(6845)索引寄存器
#define CRT_DATA_REG 0x3D5  // CRT(6845)数据寄存器

#define CRT_START_ADDR_H 0xC  // 显示内存起始位置 - 高位
#define CRT_START_ADDR_L 0xD  // 显示内存起始位置 - 低位
#define CRT_CURSOR_H 0xE      // 光标位置 - 高位
#define CRT_CURSOR_L 0xF      // 光标位置 - 低位

#define MEM_BASE 0xB8000               // 显卡内存起始位置
#define MEM_SIZE 0x4000                // 显卡内存大小
#define MEM_END (MEM_BASE + MEM_SIZE)  // 显卡内存结束位置
#define WIDTH 80                       // 屏幕文本列数
#define HEIGHT 25                      // 屏幕文本行数
#define ROW_SIZE (WIDTH * 2)           // 每行字节数
#define SCR_SIZE (ROW_SIZE * HEIGHT)   // 屏幕字节数

#define NUL 0x00
#define ENQ 0x05
#define ESC 0x1B  // ESC
#define BEL 0x07  // \a
#define BS 0x08   // \b
#define HT 0x09   // \t
#define LF 0x0A   // \n
#define VT 0x0B   // \v
#define FF 0x0C   // \f
#define CR 0x0D   // \r
#define DEL 0x7F

// static : not exported

static u32 screen;  // 当前显示器开始内存位置

static u32 pos;  // 当前光标内存位置

static u32 x, y;  // current curser coord

static u8  attr  = 7;       // char style
static u16 erase = 0x0720;  // space

// get current starting pos
static void get_screen()
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H);  // high addr of starting pos
    screen = inb(CRT_DATA_REG) << 8;       // higher bits
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);
    screen |= inb(CRT_DATA_REG);

    screen <<= 1;
    screen += MEM_BASE;
}

// set current start pos of screen
static void set_screen()
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H);  // high addr of starting pos
    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 9) & 0xff);
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);

    outb(CRT_DATA_REG, ((screen - MEM_BASE) >> 1) & 0xff);
}

// get current cursor pos
static void get_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    pos = inb(CRT_DATA_REG) << 8;
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    pos |= inb(CRT_DATA_REG);

    get_screen();

    pos <<= 1;

    pos += MEM_BASE;

    u32 delta = (pos - screen) >> 1;
    x         = delta & WIDTH;
    y         = delta / WIDTH;
}

static void set_cursor()
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 9) & 0xff);
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    outb(CRT_DATA_REG, ((pos - MEM_BASE) >> 1) & 0xff);
}

void console_clear()
{
    screen = MEM_BASE;
    pos    = MEM_BASE;
    x = y = 0;
    set_cursor();
    set_screen();

    u16* ptr = (u16*)MEM_BASE;
    while (ptr < MEM_END) {
        *ptr++ = erase;
    }
}

static void scroll_up()
{
    // enough space for an extra row
    if (screen + SCR_SIZE + ROW_SIZE < MEM_END) {
        u16* ptr = (u16*)(screen + SCR_SIZE);
        // clear the new row
        for (size_t i = 0; i < WIDTH; i++) {
            *ptr++ = erase;
        }
        // move screen down by one row
        screen += ROW_SIZE;
        pos += ROW_SIZE;
    }
    else {
        // copy the screen to start of vram
        memcpy((void*)MEM_BASE, (void*)screen, SCR_SIZE);
        pos -= (screen - MEM_BASE);
        screen = MEM_BASE;
    }

    set_screen();
}

static void command_lf()
{
    if (y + 1 < HEIGHT) {
        y++;
        pos += ROW_SIZE;
        return;
    }
    scroll_up();
}

static void command_cr()
{
    pos -= (x << 1);
    x = 0;
}

static void command_del()
{
    *(u16*)pos = erase;
}

static void command_bs()
{
    if (x) {
        x--;
        pos -= 2;
        *(u16*)pos = erase;
    }
}

void console_init()
{
    //     // console_clear();
    //     // get_screen();
    //     screen = 80 * 2 + MEM_BASE;
    //     set_screen();
    //     get_screen();
    pos = 124 + MEM_BASE;
    set_cursor();
    console_clear();
}

void console_write(char* buf, u32 count)
{
    char  ch;
    // char* ptr = (char*)pos;
    while (count--) {
        ch = *buf++;
        switch (ch) {
            case NUL: break;
            case ENQ: break;
            case ESC: break;
            case BEL: break;
            case BS: command_bs(); break;
            case HT: break;
            case LF:
                command_lf();
                command_cr();
                break;
            case VT: break;
            case FF: command_lf(); break;
            case CR: break;
            case DEL: command_del(); break;
            default:
                if (x >= WIDTH) {
                    x -= WIDTH;
                    pos -= ROW_SIZE;
                    command_lf();
                }
                char* ptr = (char*) (screen + (ROW_SIZE) * y + x * 2);
                *ptr++ = ch;
                *ptr++ = attr;
                pos += 2;
                x++;

                break;
        }
    }
    set_cursor();
}